package me.muphy.frp;

import java.io.*;
import java.math.BigInteger;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.concurrent.Executors;
import java.util.regex.Pattern;

public class TcpFrpClient extends Thread {

    private Socket server = null;
    private final String serverIp;
    private final int serverPort;
    private Socket app = null;

    private static InputStream serverInputStream = null; //服务端输入流
    private static OutputStream serverOutputStream = null;  //服务端输出流
    private static InputStream appInputStream = null; //服务端输入流
    private static OutputStream appOutputStream = null;  //服务端输出流
    private final String appIp;
    private final int appPort;


    public TcpFrpClient(String serverIp, int serverPort, String appIp, int appPort) throws IOException {
        this.appIp = appIp;
        this.appPort = appPort;
        this.serverIp = serverIp;
        this.serverPort = serverPort;
        System.out.println("应用：" + appIp + ":" + appPort + "，外网：" + serverIp + ":" + serverPort);
    }

    public static void main(String[] args) throws IOException {
        System.out.println("usage: java ProxyClient [外网:localhost 6010]  [内网:localhost 8080]");
        String serverIp = "58.16.134.114";
        int serverPort = 48981;
        String appIp = "192.168.2.15";
        int appPort = 8080;
        if (args.length > 0) {
            serverIp = args[0];
        }
        if (args.length > 1) {
            serverPort = Integer.parseInt(args[1]);
        }
        if (args.length > 2) {
            appIp = args[2];
        }
        if (args.length > 3) {
            appPort = Integer.parseInt(args[3]);
        }
        new TcpFrpClient(serverIp, serverPort, appIp, appPort).start();
    }

    @Override
    public void run() {
        // 线程运行函数
        if (!getServer()) {
            System.out.println("连接服务器失败！");
            return;
        }
        Executors.newFixedThreadPool(1).execute(() -> {
            while (true) {
                if (server == null || server.isClosed()) {
                    if (!getServer()) {
                        System.out.println("连接服务器失败！");
                        return;
                    }
                }
                byte[] buf = new byte[1024];
                int len;
                while (true) {
                    try {
                        len = serverInputStream.read(buf, 0, buf.length);
                    } catch (IOException e) {
                        e.printStackTrace();
                        close(serverInputStream, serverOutputStream, server);
                        server = null;
                        break;
                    }
                    if (len < 0) {
                        close(serverInputStream, serverOutputStream, server);
                        server = null;
                        break;
                    }
                    String suitableString = getSuitableString(buf, len);
                    System.out.println("外网：" + suitableString);
                    if ("close".equals(suitableString)) {
                        close(appInputStream, appOutputStream, app);
                        app = null;
                        continue;
                    }
                    if (app == null || app.isClosed()) {
                        if (!getApp()) {
                            System.out.println("连接内网应用失败！");
                        }
                    }
                    boolean close = suitableString.endsWith("close");
                    if (close) {
                        try {
                            appOutputStream.write(buf, 0, len - 5);
                            appOutputStream.flush();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        close(appInputStream, appOutputStream, app);
                        app = null;
                        continue;
                    }
                    try {
                        appOutputStream.write(buf, 0, len);
                        appOutputStream.flush();
                    } catch (Exception e) {
                        e.printStackTrace();
                        close(appInputStream, appOutputStream, app);
                        app = null;
                        byte[] bytes = "close".getBytes();
                        try {
                            serverOutputStream.write(bytes, 0, bytes.length);
                            serverOutputStream.flush();
                        } catch (IOException ex) {
                            ex.printStackTrace();
                            close(serverInputStream, serverOutputStream, server);
                            break;
                        }
                    }
                }
            }
        });


    }

    private boolean getServer() {
        try {
            server = new Socket();
            server.connect(new InetSocketAddress(serverIp, serverPort), 2000);
            System.out.println("外网服务器连接成功：" + serverIp + ":" + serverPort);
            serverInputStream = server.getInputStream();
            serverOutputStream = server.getOutputStream();
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        close(serverInputStream, serverOutputStream, server);
        System.exit(0);
        return false;
    }

    private boolean getApp() {
        app = new Socket();
        try {
            app.connect(new InetSocketAddress(this.appIp, this.appPort), 2000);
            appInputStream = new DataInputStream(app.getInputStream());
            appOutputStream = new DataOutputStream(app.getOutputStream());
            System.out.println("外网服务器连接成功：" + this.appIp + ":" + this.appPort);
        } catch (IOException e) {
            app = null;
            e.printStackTrace();
            close(appInputStream, appOutputStream, app);
            byte[] bytes = "close".getBytes();
            try {
                serverOutputStream.write(bytes, 0, bytes.length);
                serverOutputStream.flush();
            } catch (IOException ex) {
                ex.printStackTrace();
                close(serverInputStream, serverOutputStream, server);
                server = null;
            }
            return false;
        }
        Executors.newFixedThreadPool(1).execute(() -> {
            int len;
            byte[] buf = new byte[1024];
            try {
                while ((len = appInputStream.read(buf, 0, buf.length)) > -1) {
                    System.out.println("应用：" + getSuitableString(buf, len));
                    serverOutputStream.write(buf, 0, len);
                    serverOutputStream.flush();
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                close(appInputStream, appOutputStream, app);
                byte[] bytes = "close".getBytes();
                try {
                    serverOutputStream.write(bytes, 0, bytes.length);
                    serverOutputStream.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                    close(serverInputStream, serverOutputStream, server);
                    server = null;
                }
            }
        });
        return true;
    }

    public static String getSuitableString(byte[] bytes) {
        return getSuitableString(bytes, bytes.length);
    }

    public static String getSuitableString(byte[] bytes, int len) {
        if (bytes == null || bytes.length == 0) {
            return "";
        }
        byte[] buffer = new byte[len];
        System.arraycopy(bytes, 0, buffer, 0, len);
        String suitableParseString = new String(buffer);
        if (true) {
            return suitableParseString;
        }
        if (Pattern.compile("^[0-9a-zA-Z\\-\\+\\.\\*\\s\\!\\@\\(\\)\\#\\=\\?\\,\\$~\\|\\&\\^\\_]*$").matcher(suitableParseString).matches()) {
            return "ASCII>" + suitableParseString;
        }
        suitableParseString = new BigInteger(1, buffer).toString(16);
        if (Pattern.compile("^[0-9a-zA-Z\\-\\+\\.\\*\\s\\!\\@\\(\\)\\#\\=\\?\\,\\$~\\|\\&\\^\\_]*$").matcher(suitableParseString).matches()) {
            suitableParseString = toHexFormal(suitableParseString);
            return "HEX>" + suitableParseString;
        }
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < buffer.length; i++) {
            stringBuilder.append(String.valueOf(buffer[i]));
            if (i < buffer.length - 1) {
                stringBuilder.append(" ");
            }
        }
        return "UNKNOWN>" + stringBuilder.toString();
    }

    public static String toHexFormal(String src) {
        if (src == null) {
            return "";
        }
        src = src.replaceAll("\\s+", "").replaceAll("[^0-9a-fA-FX]", "");
        char[] chars = src.toCharArray();
        StringBuilder sb = new StringBuilder();
        int i = 0, len = chars.length % 2 == 0 ? chars.length : chars.length - 1;
        for (; i < len; i++) {
            sb.append(chars[i]);
            sb.append(chars[++i]);
            sb.append(" ");
        }
        if (i < chars.length) {
            sb.append(chars[i]);
        }
        return sb.toString().trim().toUpperCase();
    }

    /**
     * 关闭所有流
     */
    private static void close(Closeable... closeables) {
        if (closeables != null) {
            for (int i = 0; i < closeables.length; i++) {
                if (closeables[i] != null) {
                    try {
                        closeables[i].close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
